import bpy
from uuid import uuid4
from typing import List, Union
from ...addon.naming import FluidLabNaming
from bpy.types import Operator, Object
from bpy.props import BoolProperty
from ...libs.functions.basics import set_active_object
from ...libs.functions.collections import create_new_collection
from ...libs.functions.geometry_nodes import add_node, connect_nodes
from ...libs.functions.modifiers import create_modifier
from ...libs.functions.object import add_single_vertext_ob
from ...libs.functions.get_common_vars import get_common_vars
# from .op_list_reuse_base import FLUIDLAB_OT_list_reuse_base


class FLUIDLAB_OT_fluid_colliders_list_add_single(Operator):
    bl_idname = "fluidlab.fluid_colliders_list_add_single"
    bl_label = "Add New Colliders"
    bl_description = "Add Colliders to List"
    bl_options = {"REGISTER", "UNDO"}
    
    single_mode: BoolProperty(default=True)


    def add_to_list(self, fluid_colliders, fluid_single_colliders, colliders: List[Object], coll_type) -> None:

        id_name = str(uuid4())[:6]
        total = fluid_colliders.length
        padding = len(str(total))+1
                    
        name = colliders[0].name if len(colliders) == 1 else "multi_ob_"
        label_txt = name +  str(total).zfill(padding)

        # me aseguro de no meter duplicados por si acaso:
        no_dupli_colliders = set()
        for collider_ob in colliders:
            no_dupli_colliders.add(collider_ob)

        colliders = list(no_dupli_colliders)
        
        # los agregamos siempre de manera simple al fluid single colliders list:
        for collider_ob in colliders:
            item = fluid_single_colliders.add_item(id_name, collider_ob.name, collider_ob)

        return fluid_colliders.add_item(id_name, label_txt, colliders, coll_type, single_mode=self.single_mode, gn_mode=False)


    def create_collision_mod(self, ob, mod_type):
        # Si existiera algún modifier collision, se lo borramos. (Blender solo permite 1 modifier collision, por eso lo borro primero)
        next((ob.modifiers.remove(mod) for mod in ob.modifiers if mod.type == mod_type), None)
        coll_mod = create_modifier(ob, FluidLabNaming.COLLISION_MOD, mod_type)
        return coll_mod


    def invoke(self, context, event):
        selected_objects = len([ob for ob in context.selected_objects if ob.type == 'MESH'])
        if selected_objects > 1:
            return context.window_manager.invoke_props_dialog(self, width=250)
        else:
            return self.execute(context)
    
    
    def draw(self, context):
        layout = self.layout
        layout.use_property_decorate = False
        layout.use_property_split = False
        selected_objects = str(len([ob for ob in context.selected_objects if ob.type == 'MESH']))

        layout.box().label(text=f"You have {selected_objects} items selected right now.", icon='INFO')
        layout.prop(self, "single_mode", text="Use a single item for the listing?")
    

    def execute(self, context):

        objects = context.selected_objects 
        if len(objects) < 1:
            self.report({'ERROR'}, "Invalid selected objects!")
            return {'CANCELLED'}

        mod_type = 'COLLISION'
        fluid_colliders, fluid_single_colliders = get_common_vars(context, get_fluid_colliders=True, get_fluid_single_colliders=True)
        all_prev_colliders = fluid_colliders.get_all_colliders    
        
        # Prevenimos agregar objetos con particulas de liquido como colliders:
        objects_with_ps = [ob for ob in objects if len(ob.particle_systems)>0]
        if len(objects_with_ps):
            self.report({'ERROR'}, f"{[ob.name for ob in objects_with_ps]} these objects have particles!")
            return {'CANCELLED'}

        # Chequeamos que ningún objeto este previamente en nuestro listado:
        new_colliders = []
        already_in_list = []
        [already_in_list.append(ob) if ob in all_prev_colliders else new_colliders.append(ob) for ob in objects]

        # Si algún objeto ya esta en este listado cancelamos y avisamos al usuario:
        if len(already_in_list) > 0:
            self.report({'ERROR'}, f"{[ob.name for ob in already_in_list]} objects are already on this list!")
            return {'CANCELLED'}
        else:

            # Asignamos el modifier collision los objetos validos:

            all_coll_mods = {}
            if self.single_mode:
                
                for ob in new_colliders:
                    coll_mod = self.create_collision_mod(ob, mod_type)
                    all_coll_mods[coll_mod] = None
                
                # Se guardan los colliders en el listado como una lista:
                item = self.add_to_list(fluid_colliders, fluid_single_colliders, new_colliders, FluidLabNaming.COLLIDER_TYPE_LIST)
                for k in all_coll_mods.keys():
                    all_coll_mods[k] = item
                
            else:

                for ob in new_colliders:
                    coll_mod = self.create_collision_mod(ob, mod_type)

                    # Se guarda cada collider como un nuevo item en el listado:
                    item = self.add_to_list(fluid_colliders, fluid_single_colliders, [ob], FluidLabNaming.COLLIDER_TYPE_OB)
                    all_coll_mods[coll_mod] = item
            
                
            # Seteo algunas opciones a los modifiers de collision como el Damping y el Friction:
            for coll_mod, item in all_coll_mods.items():
                coll_mod.settings.damping_factor = item.settings.get_default_properties("damping_factor")
                coll_mod.settings.friction_factor = item.settings.get_default_properties("friction_factor")


        return {'FINISHED'}


class FLUIDLAB_OT_fluid_colliders_list_add_gn_collider(Operator):
    bl_idname = "fluidlab.fluid_colliders_list_add_gn_collider"
    bl_label = "Add New GN Colliders"
    bl_description = "Add Geometry Nodes Colliders to List"
    bl_options = {"REGISTER", "UNDO"}
    

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self, width=250)
    

    def draw(self, context):

        layout = self.layout
        layout.use_property_decorate = False
        layout.use_property_split = False

        fluid_colliders = get_common_vars(context, get_fluid_colliders=True)
        fluid_colliders.colliders_coll

        layout.prop(fluid_colliders, "colliders_coll", text="Collection")
    

    def execute(self, context):

        fluid_colliders = get_common_vars(context, get_fluid_colliders=True)
        
        colliders_coll = fluid_colliders.colliders_coll
        if not colliders_coll:
            self.report({'WARNING'}, "Invalid Collection!")
            return {'CANCELLED'}
        
        objects = list(colliders_coll.objects)

        if len(objects) < 1:
            self.report({'ERROR'}, "Invalid emitters objects!")
            return {'CANCELLED'}

        mod_type = 'COLLISION'
        all_prev_colliders = fluid_colliders.get_all_colliders    
        
        # Prevenimos agregar objetos con particulas de liquido como colliders:
        objects_with_ps = [ob for ob in objects if FluidLabNaming.PS_NAME in ob.particle_systems]
        if len(objects_with_ps):
            self.report({'ERROR'}, f"{[ob.name for ob in objects_with_ps]} these objects have particles!")
            return {'CANCELLED'}

        # Chequeamos que ningún objeto este previamente en nuestro listado:
        new_colliders = []
        already_in_list = []
        [already_in_list.append(ob) if ob in all_prev_colliders else new_colliders.append(ob) for ob in objects]

        id_name = str(uuid4())[:6]
        total = fluid_colliders.length
        padding = len(str(total))+1
        coll_name = colliders_coll.name
        label_txt = coll_name + "_" + str(total).zfill(padding)
        coll_mod = None

        # Si algún objeto ya esta en este listado cancelamos y avisamos al usuario:
        if len(already_in_list) > 0:

            self.report({'ERROR'}, f"{[ob.name for ob in already_in_list]} objects are already on this list!")
            return {'CANCELLED'}
        
        else:

            inter_coll = bpy.data.collections.get(FluidLabNaming.GN_COLLIDERS)
            if not inter_coll:
                inter_coll = create_new_collection(context, FluidLabNaming.GN_COLLIDERS)
            
            # set_active_collection_by_name(context, FluidLabNaming.GN_COLLIDERS)
            
            # Agrego el single object con un single vertex para ponerle el GN:
            single_vtx_ob = add_single_vertext_ob(label_txt, label_txt, (0, 0, 0), (0, 0, 0), inter_coll)
            single_vtx_ob.hide_render = True
            gn_mod = create_modifier(single_vtx_ob, FluidLabNaming.COLLISION_MOD_GN, 'NODES')

            # Asigno un node_group al GN modifier:
            node_group = bpy.data.node_groups.new("FL_COLLISION_GN", 'GeometryNodeTree')
            gn_mod.node_group = node_group

            # Nodos Input / Output del node group:
            ngroup_input = add_node(node_group, (-800, 50), 'NodeGroupInput', False)
            ngroup_output = add_node(node_group, (650, 50), 'NodeGroupOutput', False)

            # Agregando sockets de Geometry:
            node_group.interface.new_socket("Geometry", description="Geometry", in_out='INPUT', socket_type='NodeSocketGeometry', parent=None)
            node_group.interface.new_socket("Geometry", description="Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry', parent=None)

            CollectionInfo = add_node(node_group, (-430, 105), "GeometryNodeCollectionInfo", False)
            CollectionInfo.inputs[0].default_value = colliders_coll
            CollectionInfo.transform_space = 'RELATIVE'

            RealizeInstances = add_node(node_group, (-230, 105), "GeometryNodeRealizeInstances", False)

            #-------------------------------------------------------------------------------------------------
            # Haciendo los links
            #-------------------------------------------------------------------------------------------------
            connect_nodes(node_group, CollectionInfo, "Instances", RealizeInstances, "Geometry", debug=False)
            connect_nodes(node_group, RealizeInstances, "Geometry", ngroup_output, "Geometry", debug=False)
            #-------------------------------------------------------------------------------------------------

            single_vtx_ob.select_set(True)
            set_active_object(context, single_vtx_ob)

            next((single_vtx_ob.modifiers.remove(mod) for mod in single_vtx_ob.modifiers if mod.type == mod_type), None)
            coll_mod = create_modifier(single_vtx_ob, FluidLabNaming.COLLISION_MOD, mod_type)
        
        # Agregamos el item al listado de Colliders:
        item = fluid_colliders.add_item(id_name, label_txt, [single_vtx_ob], FluidLabNaming.COLLIDER_TYPE_LIST, single_mode=True, gn_mode=True)

        # Seteo el damping y el friction con los por default que tenga puestos:
        if coll_mod is not None:
            coll_mod.settings.damping_factor = item.settings.damping_factor
            coll_mod.settings.friction_factor = item.settings.friction_factor

        # Oculto los colliders originales:
        [ob.hide_set(True) for ob in colliders_coll.objects]
        
        return {'FINISHED'}
    
    